﻿// Code by Pete Blois http://blois.us/blog/2009/04/datatrigger-bindings-on-non.html
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Interactivity;
using System.Windows.Markup;
using System.Windows.Media;

namespace Outcoder.UI.Xaml.Interactivity
{
	[ContentProperty("States")]
	public class SwitchStateBehavior : Behavior<FrameworkElement>
	{
		public static readonly DependencyProperty BindingProperty 
			= DependencyProperty.Register(
				"Binding", 
				typeof(Binding), 
				typeof(SwitchStateBehavior),
		        new PropertyMetadata(null, HandleBindingChanged));

		public static readonly DependencyProperty StatesProperty 
			= DependencyProperty.Register(
				"States", 
				typeof(List<DataStateSwitchCase>),
		        typeof(SwitchStateBehavior), 
				new PropertyMetadata(null));

		readonly BindingListener listener;

		public SwitchStateBehavior()
		{
			listener = new BindingListener(HandleBindingValueChanged);
			States = new List<DataStateSwitchCase>();
		}

		public Binding Binding
		{
			get
			{
				return (Binding)GetValue(BindingProperty);
			}
			set
			{
				SetValue(BindingProperty, value);
			}
		}

		public List<DataStateSwitchCase> States
		{
			get
			{
				return (List<DataStateSwitchCase>)GetValue(StatesProperty);
			}
			set
			{
				SetValue(StatesProperty, value);
			}
		}

		static void HandleBindingChanged(
			DependencyObject sender, DependencyPropertyChangedEventArgs e)
		{
			((SwitchStateBehavior)sender).OnBindingChanged(e);
		}

		protected virtual void OnBindingChanged(DependencyPropertyChangedEventArgs e)
		{
			listener.Binding = (Binding)e.NewValue;
		}

		protected override void OnAttached()
		{
			base.OnAttached();
			FrameworkElement element = AssociatedObject;
			listener.Element = AssociatedObject;
			if (element != null)
			{
				element.Loaded += element_Loaded;
			}
		}

		void element_Loaded(object sender, RoutedEventArgs e)
		{
			FrameworkElement element = sender as FrameworkElement;
			if (element == null)
			{
				return;
			}
			element.Loaded -= element_Loaded;
			CheckState();
		}

		protected override void OnDetaching()
		{
			base.OnDetaching();
			listener.Element = null;
		}

		void HandleBindingValueChanged(object sender, BindingChangedEventArgs e)
		{
			CheckState();
		}

		void CheckState()
		{
			Control targetControl = FindTargetControl(AssociatedObject);
			if (targetControl == null)
			{
				return;
			}

			foreach (DataStateSwitchCase switchCase in States)
			{
				if (switchCase.IsValid(listener.Value))
				{
					VisualStateManager.GoToState(targetControl, switchCase.State, true);
					break;
				}
			}
		}

		internal static Control FindTargetControl(FrameworkElement element)
		{
			FrameworkElement parent = element;
			bool foundVsm = false;

			while (parent != null)
			{
				if (!foundVsm)
				{
					IList groups = VisualStateManager.GetVisualStateGroups(parent);
					if (groups != null && groups.Count > 0)
					{
						foundVsm = true;
					}
				}

				if (foundVsm)
				{
					var parentControl = parent as Control;
					if (parentControl != null)
					{
						return parentControl;
					}
				}
				parent = VisualTreeHelper.GetParent(parent) as FrameworkElement;
			}
			
			return null;
		}
	}

	public class DataStateSwitchCase
	{
		public string Value { get; set; }
		public string State { get; set; }

		public bool IsValid(object targetValue)
		{
			if (targetValue == null || Value == null)
			{
				return Equals(targetValue, Value);
			}

			object convertedValue = ImplicitTypeConverter.ConvertToType(Value, targetValue.GetType());
			return Equals(targetValue, convertedValue);
		}
	}

	static class ImplicitTypeConverter
	{
		public static object ConvertToType(object value, Type type)
		{
			ArgumentValidator.AssertNotNull(type, "type");

			if (value == null || type.IsAssignableFrom(value.GetType()))
			{
				return value;
			}

			TypeConverter converter = GetTypeConverter(type);

			if (converter != null && converter.CanConvertFrom(value.GetType()))
			{
				value = converter.ConvertFrom(value);
				return value;
			}

			return null;
		}

		public static TypeConverter GetTypeConverter(Type type)
		{
			var attribute = (TypeConverterAttribute)Attribute.GetCustomAttribute(
								type, typeof(TypeConverterAttribute), false);

			if (attribute != null)
			{
				try
				{
					Type converterType = Type.GetType(attribute.ConverterTypeName, false);
					if (converterType != null)
					{
						return Activator.CreateInstance(converterType) as TypeConverter;
					}
				}
				catch (Exception)
				{
					/* Suppress. */
				}
			}
			return new FromStringConverter(type);
		}
	}

	public class FromStringConverter : TypeConverter
	{
		readonly Type type;

		public FromStringConverter(Type type)
		{
			this.type = type;
		}

		public override bool CanConvertFrom(
			ITypeDescriptorContext context, Type sourceType)
		{
			return sourceType == typeof(string) 
					|| base.CanConvertFrom(context, sourceType);
		}

		public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
		{
			var stringValue = value as string;
			if (stringValue != null)
			{
				if (type == typeof(bool))
				{
					return bool.Parse(stringValue);
				}

				if (type.IsEnum)
				{
					return Enum.Parse(type, stringValue, false);
				}

				if (type == typeof(Guid))
				{
					return new Guid(stringValue);
				}

				var stringBuilder = new StringBuilder();
				stringBuilder.Append("<ContentControl xmlns='http://schemas.microsoft.com/client/2007' xmlns:c='"
				                     + ("clr-namespace:" + type.Namespace + ";assembly=" + type.Assembly.FullName.Split(new[] {','})[0]) + "'>\n");
				stringBuilder.Append("<c:" + type.Name + ">\n");
				stringBuilder.Append(stringValue);
				stringBuilder.Append("</c:" + type.Name + ">\n");
				stringBuilder.Append("</ContentControl>");
				var instance = XamlReader.Load(stringBuilder.ToString()) as ContentControl;
				if (instance != null)
				{
					return instance.Content;
				}
			}
			return base.ConvertFrom(context, culture, value);
		}
	}

	public class IfElseStateBehavior : Behavior<FrameworkElement>
	{
		public static readonly DependencyProperty BindingProperty 
			= DependencyProperty.Register(
				"Binding", 
				typeof(Binding), 
				typeof(IfElseStateBehavior),
				new PropertyMetadata(null, HandleBindingChanged));

		public static readonly DependencyProperty ValueProperty 
			= DependencyProperty.Register(
				"Value", 
				typeof(string), 
				typeof(IfElseStateBehavior),
				new PropertyMetadata(null, HandleValueChanged));

		public static readonly DependencyProperty TrueStateProperty 
			= DependencyProperty.Register(
				"TrueState", 
				typeof(string), 
				typeof(IfElseStateBehavior),
				new PropertyMetadata(null));

		public static readonly DependencyProperty FalseStateProperty 
			= DependencyProperty.Register(
				"FalseState", 
				typeof(string), 
				typeof(IfElseStateBehavior),
				new PropertyMetadata(null));

		readonly BindingListener listener;

		public IfElseStateBehavior()
		{
			listener = new BindingListener(HandleBindingValueChanged);
		}

		public Binding Binding
		{
			get
			{
				return (Binding)GetValue(BindingProperty);
			}
			set
			{
				SetValue(BindingProperty, value);
			}
		}

		public string Value
		{
			get
			{
				return (string)GetValue(ValueProperty);
			}
			set
			{
				SetValue(ValueProperty, value);
			}
		}

		public string FalseState
		{
			get
			{
				return (string)GetValue(FalseStateProperty);
			}
			set
			{
				SetValue(FalseStateProperty, value);
			}
		}

		public string TrueState
		{
			get
			{
				return (string)GetValue(TrueStateProperty);
			}
			set
			{
				SetValue(TrueStateProperty, value);
			}
		}

		static void HandleBindingChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
		{
			((IfElseStateBehavior)sender).OnBindingChanged(e);
		}

		protected virtual void OnBindingChanged(DependencyPropertyChangedEventArgs e)
		{
			listener.Binding = (Binding)e.NewValue;
		}

		static void HandleValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
		{
			((IfElseStateBehavior)sender).OnValueChanged(e);
		}

		protected virtual void OnValueChanged(DependencyPropertyChangedEventArgs e)
		{
			CheckState();
		}

		protected override void OnAttached()
		{
			base.OnAttached();

			FrameworkElement element = AssociatedObject;
			listener.Element = AssociatedObject;
			if (element != null)
			{
				element.Loaded += element_Loaded;
			}
		}

		void element_Loaded(object sender, RoutedEventArgs e)
		{
			FrameworkElement element = sender as FrameworkElement;
			if (element == null)
			{
				return;
			}
			element.Loaded -= element_Loaded;
			CheckState();
		}

		protected override void OnDetaching()
		{
			base.OnDetaching();
			listener.Element = null;
		}

		void HandleBindingValueChanged(object sender, BindingChangedEventArgs e)
		{
			CheckState();
		}

		void CheckState()
		{
			if (Value == null || listener.Value == null)
			{
				IsTrue = Equals(listener.Value, Value);
			}
			else
			{
				object convertedValue = ImplicitTypeConverter.ConvertToType(Value, listener.Value.GetType());
				IsTrue = Equals(listener.Value, convertedValue);
			}
		}

		bool? isTrue;

		bool? IsTrue
		{
			get
			{
				return isTrue;
			}
			set
			{
				/* HACK: The initial state is not being set, hence the condition is commented. */
				//if (isTrue != value)
				{
					isTrue = value;

					if (isTrue.HasValue)
					{
						Control targetControl = SwitchStateBehavior.FindTargetControl(AssociatedObject);
						if (targetControl == null)
						{
							isTrue = null;
						}
						else if (isTrue.Value)
						{
							VisualStateManager.GoToState(targetControl, TrueState, true);
						}
						else
						{
							VisualStateManager.GoToState(targetControl, FalseState, true);
						}
					}
				}
			}
		}
	}
}